<template>
	<div v-transfer-dom :data-transfer="getTransfer">
		<transition :name="transitionNames[1]">
			<div
				:class="maskClasses"
				:style="wrapStyles"
				v-show="visible"
				v-if="showMask"
				@click="handleMask"
			></div>
		</transition>
		<div :class="wrapClasses" :style="wrapStyles" @click="handleWrapClick">
			<transition :name="transitionNames[0]" @after-leave="animationFinish">
				<div :class="classes" :style="mainStyles" v-show="visible" @mousedown="handleMousedown">
					<div :class="contentClasses" ref="content" :style="contentStyles" @click="handleClickModal">
						<a :class="[prefixCls + '-close']" v-if="closable" @click="close">
							<slot name="close">
								<Icon type="ios-close"></Icon>
							</slot>
						</a>
						<div :class="[prefixCls + '-header']" @mousedown="handleMoveStart" v-if="showHead">
							<slot name="header">
								<div :class="[prefixCls + '-header-inner']">{{ title }}</div>
							</slot>
						</div>
						<div :class="[prefixCls + '-body']">
							<slot></slot>
						</div>
						<div :class="[prefixCls + '-footer']" v-if="!footerHide">
							<slot name="footer">
								<Button type="text" @click.native="cancel">{{ localeCancelText }}</Button>
								<Button type="primary" :loading="buttonLoading" @click.native="ok">{{ localeOkText }}</Button>
							</slot>
						</div>
					</div>
				</div>
			</transition>
		</div>
	</div>
</template>
<script lang="ts">
	import { Prop, Component, Watch } from "vue-property-decorator";
	import Anes from "../base";
	import Icon from "../icon";
	import Button from "../button";
	import TransferDom from "../../directives/transfer-dom";
	import ScrollbarMixins from "../../mixins/scrollbar";
	import { findComponentsDownward } from "../../utils/assist";
	import {
		transferIndex as modalIndex,
		transferIncrease as modalIncrease
	} from "../../utils/const";

	class DragData {
		x = 0;
		y = 0;
		dragX!: number;
		dragY!: number;
		dragging = false;
	}
	@Component({
		components: { Icon, Button },
		directives: { TransferDom },
		mixins: [ScrollbarMixins]
	})
	export default class Modal extends Anes {
		@Prop({ type: Boolean, default: false }) value!: boolean;
		@Prop({ type: Boolean, default: true }) closable!: boolean;
		@Prop({ type: Boolean, default: true }) maskClosable?: boolean;
		@Prop({ type: Boolean, default: false }) loading!: boolean;
		@Prop({ type: Boolean, default: false }) footerHide!: boolean;
		@Prop({ type: Boolean, default: false }) scrollable!: boolean;
		@Prop({ type: Boolean, default: false }) fullscreen!: boolean;
		@Prop({ type: Boolean, default: true }) mask!: boolean;
		@Prop({ type: Boolean, default: false }) draggable!: boolean;
		@Prop({ type: Number, default: 1000 }) zIndex!: number;
		@Prop({ type: [Number, String], default: 520 }) width!: number | string;
		@Prop() le!: string;
		@Prop() title!: string;
		@Prop() okText!: string;
		@Prop() cancelText!: string;
		@Prop() className!: string;
		@Prop() styles!: CSSStyleDeclaration;
		@Prop({
			type: Array,
			default() {
				return ["ease", "fade"];
			}
		})
		transitionNames!: Array<string>;

		timer!: number;
		prefixCls = "ivu-modal";
		wrapShow = false;
		showHead = true;
		buttonLoading = false;
		visible = this.value;
		dragData = new DragData();
		modalIndex = this.handleGetModalIndex(); // for Esc close the top modal
		isMouseTriggerIn = false; // #5800
		get getTransfer(): boolean {
			if (this.transfer == undefined) {
				if (this.$IVIEW && this.$IVIEW.transfer)
					return this.$IVIEW.transfer as boolean;
				else return false;
			}
			return this.transfer;
		}

		get wrapClasses() {
			return [
				`${this.prefixCls}-wrap`,
				{
					[`${this.prefixCls}-hidden`]: !this.wrapShow,
					[`${this.className}`]: !!this.className,
					[`${this.prefixCls}-no-mask`]: !this.showMask
				}
			];
		}
		get wrapStyles() {
			return {
				zIndex: this.modalIndex + this.zIndex
			};
		}
		get maskClasses() {
			return `${this.prefixCls}-mask`;
		}
		get classes() {
			return [
				`${this.prefixCls}`,
				{
					[`${this.prefixCls}-fullscreen`]: this.fullscreen,
					[`${this.prefixCls}-fullscreen-no-header`]:
						this.fullscreen && !this.showHead,
					[`${this.prefixCls}-fullscreen-no-footer`]:
						this.fullscreen && this.footerHide
				}
			];
		}
		get contentClasses() {
			return [
				`${this.prefixCls}-content`,
				{
					[`${this.prefixCls}-content-no-mask`]: !this.showMask,
					[`${this.prefixCls}-content-drag`]: this.draggable,
					[`${this.prefixCls}-content-dragging`]:
						this.draggable && this.dragData.dragging
				}
			];
		}
		get mainStyles() {
			const style = {};
			let width = 0;
			if (this.width as number) {
				width = this.width as number;
			} else {
				width = parseInt(this.width as string);
			}

			const styleWidth =
				this.dragData.x !== 0
					? { top: 0 }
					: { width: width <= 100 ? `${width}%` : `${width}px` };

			const customStyle = this.styles ? this.styles : {};

			Object.assign(style, styleWidth, customStyle);

			return style;
		}
		get contentStyles() {
			const style = {} as CSSStyleDeclaration;

			if (this.draggable) {
				let customTop = 0;
				if (this.styles && this.styles.top)
					customTop = parseFloat(this.styles.top);
				let customLeft = 0;
				if (this.styles && this.styles.left)
					customLeft = parseFloat(this.styles.left);
				if (this.dragData.x !== null) {
					const x = parseInt((this.dragData.x as unknown) as string);
					style.left = `${x - customLeft}px`;
				}
				if (this.dragData.y !== null) {
					const y = parseInt((this.dragData.y as unknown) as string);
					style.top = `${y - customTop}px`;
				}

				const width = parseInt(this.width as string);
				const styleWidth = {
					width: width <= 100 ? `${width}%` : `${width}px`
				};

				Object.assign(style, styleWidth);
			}

			return style;
		}
		get localeOkText() {
			if (this.okText === undefined) {
				return this.l("i.modal.okText");
			} else {
				return this.okText;
			}
		}
		get localeCancelText() {
			if (this.cancelText === undefined) {
				return this.l("i.modal.cancelText");
			} else {
				return this.cancelText;
			}
		}
		get showMask() {
			return this.draggable ? false : this.mask;
		}

		get maskClosabled() {
			if (this.maskClosable == undefined) {
				return !this.$IVIEW || this.$IVIEW.modal.maskClosable === ""
					? true
					: (this.$IVIEW.modal.maskClosable as boolean);
			}
			return this.maskClosable;
		}
		close() {
			this.visible = false;
			this.$emit("input", false);
			this.$emit("on-cancel");
		}

		handleMask() {
			if (this.maskClosabled && this.showMask) {
				this.close();
			}
		}
		handleWrapClick(event: Event) {
			if (this.isMouseTriggerIn) {
				this.isMouseTriggerIn = false;
				return;
			}
			// use indexOf,do not use === ,because ivu-modal-wrap can have other custom className
			if (event.target) {
				const className = (event.target as HTMLElement).getAttribute("class");
				if (className && className.indexOf(`${this.prefixCls}-wrap`) > -1)
					this.handleMask();
			}
		}

		handleMousedown() {
			this.isMouseTriggerIn = true;
		}
		cancel() {
			this.close();
		}
		ok() {
			if (this.loading) {
				this.buttonLoading = true;
			} else {
				this.visible = false;
				this.$emit("input", false);
			}
			this.$emit("on-ok");
		}
		EscClose(e: KeyboardEvent) {
			if (this.visible && this.closable) {
				if (e.keyCode === 27) {
					const $Modals = findComponentsDownward<Modal>(
						this.$root,
						"Modal"
					).filter(item => item.$data.visible && item.$props.closable);

					const $TopModal = $Modals.sort((a, b) => {
						return a.$data.modalIndex < b.$data.modalIndex ? 1 : -1;
					})[0];

					setTimeout(() => {
						$TopModal.close();
					}, 0);
				}
			}
		}
		animationFinish() {
			this.$emit("on-hidden");
		}
		handleMoveStart(event: MouseEvent) {
			if (!this.draggable) return false;

			const $content = this.$refs.content;
			const rect = ($content as HTMLElement).getBoundingClientRect();
			this.dragData.x = rect.x || rect.left;
			this.dragData.y = rect.y || rect.top;

			const distance = {
				x: event.clientX,
				y: event.clientY
			};

			this.dragData.dragX = distance.x;
			this.dragData.dragY = distance.y;

			this.dragData.dragging = true;

			this.on()(window, "mousemove", this.handleMoveMove);
			this.on()(window, "mouseup", this.handleMoveEnd);
		}
		handleMoveMove(event: MouseEvent) {
			if (!this.dragData.dragging) return false;

			const distance = {
				x: event.clientX,
				y: event.clientY
			};

			const diffDistance = {
				x: distance.x - this.dragData.dragX,
				y: distance.y - this.dragData.dragY
			};

			this.dragData.x += diffDistance.x;
			this.dragData.y += diffDistance.y;

			this.dragData.dragX = distance.x;
			this.dragData.dragY = distance.y;
		}
		handleMoveEnd() {
			this.dragData.dragging = false;
			this.off()(window, "mousemove", this.handleMoveMove);
			this.off()(window, "mouseup", this.handleMoveEnd);
		}
		handleGetModalIndex() {
			modalIncrease();
			return modalIndex;
		}
		handleClickModal() {
			if (this.draggable) {
				this.modalIndex = this.handleGetModalIndex();
			}
		}

		mounted() {
			if (this.visible) {
				this.wrapShow = true;
			}

			let showHead = true;

			if (this.$slots.header === undefined && !this.title) {
				showHead = false;
			}

			this.showHead = showHead;

			// ESC close
			document.addEventListener("keydown", this.EscClose);
		}
		beforeDestroy() {
			document.removeEventListener("keydown", this.EscClose);

			this.removeScrollEffect();
		}
		@Watch("value")
		onValueChang(val: boolean) {
			this.visible = val;
		}
		@Watch("visible")
		onVisibleChang(val: boolean) {
			if (val === false) {
				this.buttonLoading = false;
				this.timer = setTimeout(() => {
					this.wrapShow = false;
					this.removeScrollEffect();
				}, 300);
			} else {
				this.modalIndex = this.handleGetModalIndex();

				if (this.timer) clearTimeout(this.timer);
				this.wrapShow = true;
				if (!this.scrollable) {
					this.addScrollEffect();
				}
			}
			this.broadcast("Table", "on-visible-change", [val]);
			this.broadcast("Slider", "on-visible-change", [val]); // #2852
			this.$emit("on-visible-change", val);
		}
		@Watch("loading")
		onLoadingChange(val: boolean) {
			if (!val) {
				this.buttonLoading = false;
			}
		}
		@Watch("scrollable")
		onScrollableChange(val: boolean) {
			if (!val) {
				this.addScrollEffect();
			} else {
				this.removeScrollEffect();
			}
		}
		@Watch("title")
		onTitleChange(val: string) {
			if (this.$slots.header === undefined) {
				this.showHead = !!val;
			}
		}
	}
</script>
